<?php

/**
* phpagi-asmanager.php : PHP Asterisk Manager functions
* Website: http://phpagi.sourceforge.net
*
* Copyright (c) 2004 Matthew Asham <matthewa@bcwireless.net>
* All Rights Reserved.
*
* This software is released under the terms of the GNU Public License v2
*  A copy of which is available from http://www.fsf.org/licenses/gpl.txt
*
* @package phpAGI
*/


if (!isset($bootstrap_settings['amportal_conf_initialized']) || !$bootstrap_settings['amportal_conf_initialized']) {
    $bootstrap_settings['skip_astman'] = true;
    $restrict_mods = true;
    if (!@include_once(getenv('ISSABELPBX_CONF') ? getenv('ISSABELPBX_CONF') : '/etc/issabelpbx.conf')) {
        include_once('/etc/asterisk/issabelpbx.conf');
    }
}

define('AST_CONFIG_DIR', $amp_conf['ASTETCDIR']);
define('AST_SPOOL_DIR', $asterisk_conf['astspooldir']);
define('AST_TMP_DIR', AST_SPOOL_DIR . '/tmp/');
define('DEFAULT_PHPAGI_CONFIG', AST_CONFIG_DIR . '/phpagi.conf');

define('AST_DIGIT_ANY', '0123456789#*');

define('AGI_PORT', 4573);

define('AGIRES_OK', 200);


define('AST_STATE_DOWN', 0);
define('AST_STATE_RESERVED', 1);
define('AST_STATE_OFFHOOK', 2);
define('AST_STATE_DIALING', 3);
define('AST_STATE_RING', 4);
define('AST_STATE_RINGING', 5);
define('AST_STATE_UP', 6);
define('AST_STATE_BUSY', 7);
define('AST_STATE_DIALING_OFFHOOK', 8);
define('AST_STATE_PRERING', 9);


/**
* Asterisk Manager class
*
* @link http://www.voip-info.org/wiki-Asterisk+config+manager.conf
* @link http://www.voip-info.org/wiki-Asterisk+manager+API
* @example examples/sip_show_peer.php Get information about a sip peer
* @package phpAGI
*/
class AGI_AsteriskManager {
    /**
    * Config variables
    *
    * @var array
    * @access public
    */
    public $config;

    /**
    * Socket
    *
    * @access public
    */
    public $socket = NULL;

    /**
    * Server we are connected to
    *
    * @access public
    * @var string
    */
    public $server;

    /**
    * Port on the server we are connected to
    *
    * @access public
    * @var integer
    */
    public $port;

    /**
    * Parent AGI
    *
    * @access private
    * @var AGI
    */
    public $pagi;

    /**
    * Username we connected with (for reconnect)
    *
    * @access public
    * @var string
    */
    public $username = NULL;

    /**
    * Secret we connected with (for reconnect)
    *
    * @access public
    * @var string
    */
    public $secret = NULL;

    /**
    * Current state of events (for reconnect)
    *
    * @access public
    * @var string
    */
    public $events = NULL;

    /**
    * Number of reconnect attempts per incident
    *
    * @access public
    * @var string
    */
    public $reconnects = 2;

    /**
    * Event Handlers
    *
    * @access private
    * @var array
    */
    public $event_handlers;

    /**
    * Log Level
    *
    * @access private
    * @var integer
    */
    public $log_level;

    public $useCaching = false;
    public $memAstDB = null;

    /**
    * Constructor
    *
    * @param string $config is the name of the config file to parse or a parent agi from which to read the config
    * @param array $optconfig is an array of configuration vars and vals, stuffed into $this->config['asmanager']
    */
    function __construct($config=NULL, $optconfig=array()) {
        // load config
        if(!is_null($config) && file_exists($config)) {
            $this->config = parse_ini_file($config, true);
        } elseif(file_exists(DEFAULT_PHPAGI_CONFIG)) {
            $this->config = parse_ini_file(DEFAULT_PHPAGI_CONFIG, true);
        }

        // If optconfig is specified, stuff vals and vars into 'asmanager' config array.
        foreach($optconfig as $var=>$val) {
            $this->config['asmanager'][$var] = $val;
        }


        // add default values to config for uninitialized values
        if (!isset($this->config['asmanager']['server'])) {
            $this->config['asmanager']['server'] = 'localhost';
        }
        if (!isset($this->config['asmanager']['port'])) {
            $this->config['asmanager']['port'] = 5038;
        }
        if (!isset($this->config['asmanager']['username'])) {
            $this->config['asmanager']['username'] = 'phpagi';
        }
        if (!isset($this->config['asmanager']['secret'])) {
            $this->config['asmanager']['secret'] = 'phpagi';
        }
        if (isset($this->config['asmanager']['cachemode'])) {
            $this->useCaching = $this->config['asmanager']['cachemode'];
        }

        $this->log_level = (isset($this->config['asmanager']['log_level']) && is_numeric($this->config['asmanager']['log_level']))
                        ? $this->config['asmanager']['log_level'] : false;
        $this->reconnects = isset($this->config['asmanager']['reconnects']) ? $this->config['asmanager']['reconnects'] : 2;
    }

    function LoadAstDB(){
        if ($this->memAstDB != null) {
            unset($this->memAstDB);
        }
        $this->memAstDB = $this->database_show();
    }

    /**
    * Send a request
    *
    * @param string $action
    * @param array $parameters
    * @return array of parameters
    */
    function send_request($action, $parameters=array(), $retry=true) {
        $reconnects = $this->reconnects;

        $req = "Action: $action\r\n";
        foreach($parameters as $var=>$val) {
            if (is_array($val)) {
                foreach($val as $k => $v) {
                    $req .= "$var: $k=$v\r\n";
                }
            } else {
                $req .= "$var: $val\r\n";
            }

        }
        $req .= "\r\n";
        $this->log("Sending Request down socket:",10);
        $this->log($req,10);
        fwrite($this->socket, $req);
        $response = $this->wait_response();

        // If we got a false back then something went wrong, we will try to reconnect the manager connection to try again
        //
        while ($response === false && $retry && $reconnects > 0) {
            $this->log("Unexpected failure executing command: $action, reconnecting to manager and retrying: $reconnects");
            $this->disconnect();
            if ($this->connect($this->server.':'.$this->port, $this->username, $this->secret, $this->events) !== false) {
                fwrite($this->socket, $req);
                $response = $this->wait_response();
            } else {
                if ($reconnects > 1) {
                    $this->log("reconnect command failed, sleeping before next attempt");
                    sleep(1);
                } else {
                    $this->log("FATAL: no reconnect attempts left, command permanently failed, returning to calling program with 'false' failure code");
                }
            }
            $reconnects--;
        }
        return $response;
    }

    /**
    * Wait for a response
    *
    * If a request was just sent, this will return the response.
    * Otherwise, it will loop forever, handling events.
    *
    * @param boolean $allow_timeout if the socket times out, return an empty array
    * @return array of parameters, empty on timeout
    */
    function wait_response($allow_timeout = false) {
        $timeout = false;
        do {
            $type = NULL;
            $parameters = array();

            if (feof($this->socket) || !$this->socket) {
                $this->log("Got EOF in wait_response() from socket waiting for response, returning false",10);
                return false;
            }
            $buffer = trim(fgets($this->socket, 4096));
            while($buffer != '') {
                $a = strpos($buffer, ':');
                if($a) {
                    if(!count($parameters)) {// first line in a response?
                        $type = strtolower(substr($buffer, 0, $a));
                        if(substr($buffer, $a + 2) == 'Follows') {
                            // A follows response means there is a miltiline field that follows.
                            $parameters['data'] = '';
                            $buff = fgets($this->socket, 4096);
                            while(substr($buff, 0, 6) != '--END ') {
                                $parameters['data'] .= $buff;
                                $buff = fgets($this->socket, 4096);
                            }
                        }
                    }

                    // store parameter in $parameters
                    $parameters[substr($buffer, 0, $a)] = substr($buffer, $a + 2);

                    // Asterisk 16
                    if(substr($buffer, 0, $a)=='Output' && !isset($parameters['data'])) {
                        $parameters['data']=substr($buffer, $a + 2);
                    }

                }
                $buffer = trim(fgets($this->socket, 4096));
            }

            // process response
            switch($type) {
                case '': // timeout occured
                $timeout = $allow_timeout;
                    break;
                case 'event':
                    $this->process_event($parameters);
                    break;
                case 'response':
                case 'message':
                    break;
                default:
                    $this->log('Unhandled response packet ('.$type.') from Manager: ' . print_r($parameters, true));
                    break;
            }
        } while($type != 'response' && $type != 'message' && !$timeout);
        $this->log("returning from wait_response with with type: $type",10);
        $this->log('$parmaters: '.print_r($parameters,true),10);
        $this->log('$buffer: '.print_r($buffer,true),10);
        if (isset($buff)) {
            $this->log('$buff: '.print_r($buff,true),10);
        }
        return $parameters;
    }

    /**
    * Connect to Asterisk
    *
    * @example examples/sip_show_peer.php Get information about a sip peer
    *
    * @param string $server
    * @param string $username
    * @param string $secret
    * @return boolean true on success
    */
    function connect($server=NULL, $username=NULL, $secret=NULL, $events='on') {
        // use config if not specified
        if(is_null($server)) {
            $server = $this->config['asmanager']['server'];
        }
        $this->username = is_null($username) ? $this->config['asmanager']['username'] : $username;
        $this->secret = is_null($secret) ? $this->config['asmanager']['secret'] : $secret;
        $this->events = $events;

        // get port from server if specified
        if(strpos($server, ':') !== false) {
            $c = explode(':', $server);
            $this->server = $c[0];
            $this->port = $c[1];
        } else {
            $this->server = $server;
            $this->port = $this->config['asmanager']['port'];
        }

        // connect the socket
        $errno = $errstr = NULL;
        $this->socket = @fsockopen($this->server, $this->port, $errno, $errstr);
        if(!$this->socket) {
            $this->log("Unable to connect to manager {$this->server}:{$this->port} ($errno): $errstr");
            return false;
        }

        // read the header
        $str = fgets($this->socket);
        if($str == false) {
            // a problem.
            $this->log("Asterisk Manager header not received.");
            return false;
        } else {
            // note: don't $this->log($str) until someone looks to see why it mangles the logging
        }

        // login
        $res = $this->send_request('login',
                            array('Username'=>$this->username, 'Secret'=>$this->secret, 'Events'=>$this->events),
                            false);
        if($res['Response'] != 'Success') {
            $this->log("Failed to login.");
            $this->disconnect();
            return false;
        }
        return true;
    }

    /**
    * Disconnect
    *
    * @example examples/sip_show_peer.php Get information about a sip peer
    */
    function disconnect() {
        $this->logoff();
        fclose($this->socket);
    }

    /**
    * Check if the socket is connected
    *
    */
    function connected() {
        return (bool)$this->socket;
    }

    /**
    * Set Absolute Timeout
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+AbsoluteTimeout
    * @param string $channel
    * @param integer $timeout
    */
    function AbsoluteTimeout($channel, $timeout) {
        return $this->send_request('AbsoluteTimeout', array('Channel'=>$channel, 'Timeout'=>$timeout));
    }

    /**
    * Change monitoring filename of a channel
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ChangeMonitor
    * @param string $channel
    * @param string $file
    */
    function ChangeMonitor($channel, $file) {
        return $this->send_request('ChangeMontior', array('Channel'=>$channel, 'File'=>$file));
    }

    /**
    * Execute Command
    *
    * @example examples/sip_show_peer.php Get information about a sip peer
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Command
    * @link http://www.voip-info.org/wiki-Asterisk+CLI
    * @param string $command
    * @param string $actionid message matching variable
    */
    function Command($command, $actionid=NULL) {
        $parameters = array('Command'=>$command);
        if($actionid) {
            $parameters['ActionID'] = $actionid;
        }
        return $this->send_request('Command', $parameters);
    }

    /**
    * Enable/Disable sending of events to this manager
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Events
    * @param string $eventmask is either 'on', 'off', or 'system,call,log'
    */
    function Events($eventmask) {
    $this->events = $eventmask;
        return $this->send_request('Events', array('EventMask'=>$eventmask));
    }

    /**
    * Check Extension Status
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ExtensionState
    * @param string $exten Extension to check state on
    * @param string $context Context for extension
    * @param string $actionid message matching variable
    */
    function ExtensionState($exten, $context, $actionid = NULL) {
        $parameters = array('Exten'=>$exten, 'Context'=>$context);
        if($actionid) {
            $parameters['ActionID'] = $actionid;
        }
              return $this->send_request('ExtensionState', $parameters);
    }

    /**
    * Gets a Channel Variable
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+GetVar
    * @param string $channel  Channel to read variable from
    * @param string $variable
    * @param string $actionid message matching variable
    */
    function GetVar($channel, $variable, $actionid=NULL) {
        $parameters = array('Channel'=>$channel, 'Variable'=>$variable);
        if($actionid) {
            $parameters['ActionID'] = $actionid;
        }
        return $this->send_request('GetVar', $parameters);
    }

    /**
    * Hangup Channel
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Hangup
    * @param string $channel The channel name to be hungup
    */
    function Hangup($channel) {
        return $this->send_request('Hangup', array('Channel'=>$channel));
    }

    /**
    * List IAX Peers
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+IAXpeers
    */
    function IAXPeers() {
        return $this->send_request('IAXPeers');
    }

    function PresenceState($provider) {
        return $this->send_request('PresenceState',array('Provider'=>$provider));
    }

    /**
    * List available manager commands
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ListCommands
    * @param string $actionid message matching variable
    */
    function ListCommands($actionid=NULL) {
        if($actionid) {
            return $this->send_request('ListCommands', array('ActionID'=>$actionid));
        } else {
            return $this->send_request('ListCommands');
        }
    }

    /**
    * Logoff Manager
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Logoff
    */
    function Logoff() {
        return $this->send_request('Logoff',array(),false);
    }

    /**
    * Check Mailbox Message Count
    *
    * Returns number of new and old messages.
    *   Message: Mailbox Message Count
    *   Mailbox: <mailboxid>
    *   NewMessages: <count>
    *   OldMessages: <count>
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+MailboxCount
    * @param string $mailbox Full mailbox ID <mailbox>@<vm-context>
    */
    function MailboxCount($mailbox, $actionid=NULL) {
        $parameters = array('Mailbox'=>$mailbox);
        if($actionid) {
            $parameters['ActionID'] = $actionid;
        }
        return $this->send_request('MailboxCount', $parameters);
    }

    /**
    * Check Mailbox
    *
    * Returns number of messages.
    *   Message: Mailbox Status
    *   Mailbox: <mailboxid>
    *   Waiting: <count>
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+MailboxStatus
    * @param string $mailbox Full mailbox ID <mailbox>@<vm-context>
    * @param string $actionid message matching variable
    */
    function MailboxStatus($mailbox, $actionid=NULL) {
        $parameters = array('Mailbox'=>$mailbox);
        if($actionid) {
            $parameters['ActionID'] = $actionid;
        }
        return $this->send_request('MailboxStatus', $parameters);
    }
    
    /**
     * MessageSend
     *
     * Send an SMS message
     *
     * @param string $to
     * @param string $from
     * @param string $body
     * @param string $variable optional
     * @returns array result of send_request
    */
    function MessageSend($to, $from, $body, $variable=null) {
        $parameters['To'] = $to;
        $parameters['From'] = $from;
        $parameters['Base64Body'] = base64_encode($body);
        if($variable) {
            $parameters['Variable'] = $variable;
        }
        return $this->send_request('MessageSend', $parameters);
    }

    /**
    * Monitor a channel
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Monitor
    * @param string $channel
    * @param string $file
    * @param string $format
    * @param boolean $mix
    */
    function Monitor($channel, $file=NULL, $format=NULL, $mix=NULL) {
        $parameters = array('Channel'=>$channel);
        if($file) {
            $parameters['File'] = $file;
        }
        if($format) {
            $parameters['Format'] = $format;
        }
        if(!is_null($file)) {
            $parameters['Mix'] = ($mix) ? 'true' : 'false';
        }
        return $this->send_request('Monitor', $parameters);
    }

    /**
    * Originate Call
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Originate
    * @param string $channel
    * @param string $exten
    * @param string $context
    * @param string $priority
    * @param integer $timeout
    * @param string $callerid
    * @param string $variable
    * @param string $account
    * @param string $application
    * @param string $data
    * == exactly 11 values required ==
    *
    * -- OR --
    *
    * @pram array a key => value array of what ever you want to pass in
    */
    function Originate() {
        $num_args = func_num_args();

        if ($num_args === 10) {
            $args = func_get_args();

            $parameters = array();
            if ($args[0]) {
                $parameters['Channel'] = $args[0];
            }
            if ($args[1]) {
                $parameters['Exten'] = $args[1];
            }
            if ($args[2]) {
                $parameters['Context'] = $args[2];
            }
            if ($args[3]) {
                $parameters['Priority'] = $args[3];
            }
            if ($args[4]) {
                $parameters['Timeout'] = $args[4];
            }
            if ($args[5]) {
                 $parameters['CallerID'] = $args[5];
            }
            if ($args[6]) {
                $parameters['Variable'] = $args[6];
            }
            if ($args[7]) {
                $parameters['Account'] = $args[7];
            }
            if ($args[8]) {
                $parameters['Application'] = $args[8];
            }
            if ($args[9]) {
                $parameters['Data'] = $args[9];
            }
        } else {
            $args = func_get_args();
            $args = $args[0];
            foreach ($args as $key => $val) {
                $parameters[$key] = $val;
            }
        }

        return $this->send_request('Originate', $parameters);
    }

    /**
    * List parked calls
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ParkedCalls
    */
    function ParkedCalls($actionid=NULL) {
        if($actionid) {
            return $this->send_request('ParkedCalls', array('ActionID'=>$actionid));
        } else {
        return $this->send_request('ParkedCalls');
        }
    }

    /**
    * Ping
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Ping
    */
    function Ping() {
        return $this->send_request('Ping');
    }

       /**
    * Queue Add
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueAdd
    * @param string $queue
    * @param string $interface
    * @param integer $penalty
    */
    function QueueAdd($queue, $interface, $penalty=0) {
        $parameters = array('Queue'=>$queue, 'Interface'=>$interface);
        if($penalty) {
            $parameters['Penalty'] = $penalty;
        }
        return $this->send_request('QueueAdd', $parameters);
    }

    /**
    * Queue Remove
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueRemove
    * @param string $queue
    * @param string $interface
    */
    function QueueRemove($queue, $interface) {
         return $this->send_request('QueueRemove', array('Queue'=>$queue, 'Interface'=>$interface));
    }
    /**
    * Queues
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Queues
    */
    function Queues() {
        return $this->send_request('Queues');
    }

    /**
    * Queue Status
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueStatus
    * @param string $actionid message matching variable
    */
    function QueueStatus($actionid=NULL) {
        if($actionid) {
            return $this->send_request('QueueStatus', array('ActionID'=>$actionid));
        } else {
            return $this->send_request('QueueStatus');
        }
    }

    /**
    * Redirect
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Redirect
    * @param string $channel
    * @param string $extrachannel
    * @param string $exten
    * @param string $context
    * @param string $priority
    */
    function Redirect($channel, $extrachannel, $exten, $context, $priority) {
        return $this->send_request('Redirect', array('Channel'=>$channel, 'ExtraChannel'=>$extrachannel, 'Exten'=>$exten,
                                'Context'=>$context, 'Priority'=>$priority));
    }

    /**
    * Set the CDR UserField
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+SetCDRUserField
    * @param string $userfield
    * @param string $channel
    * @param string $append
*/
    function SetCDRUserField($userfield, $channel, $append=NULL) {
        $parameters = array('UserField'=>$userfield, 'Channel'=>$channel);
        if($append) {
            $parameters['Append'] = $append;
        }
        return $this->send_request('SetCDRUserField', $parameters);
    }

    /**
    * Set Channel Variable
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+SetVar
    * @param string $channel Channel to set variable for
    * @param string $variable name
    * @param string $value
    */
    function SetVar($channel, $variable, $value) {
        return $this->send_request('SetVar', array('Channel'=>$channel, 'Variable'=>$variable, 'Value'=>$value));
    }

    /**
    * List SIP Peers
    */
    function SIPpeers() {
    // XXX need to look at source to find this function...
        return $this->send_request('SIPpeers');
    }

    /* Channel Status
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Status
    * @param string $channel
    * @param string $actionid message matching variable
*/
    function Status($channel, $actionid=NULL) {
        $parameters = array('Channel'=>$channel);
        if ($actionid) {
            $parameters['ActionID'] = $actionid;
        }
        return $this->send_request('Status', $parameters);
    }

    /**
    * Stop monitoring a channel
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+StopMonitor
    * @param string $channel
    */
    function StopMonitor($channel) {
        return $this->send_request('StopMonitor', array('Channel'=>$channel));
    }

    /**
    * Dial over Zap channel while offhook
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDialOffhook
    * @param string $zapchannel
    * @param string $number
    */
    function ZapDialOffhook($zapchannel = '', $number = '') {
        // XXX need to look at source to find this function...
        if ($zapchannel && $number) {
            return $this->send_request('ZapDialOffhook', array('ZapChannel'=>$zapchannel, 'Number'=>$number));
        } else {
            return $this->send_request('ZapDialOffhook');
        }

    }

    /**
    * Toggle Zap channel Do Not Disturb status OFF
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDNDoff
    * @param string $zapchannel
    */
    function ZapDNDoff($zapchannel = '') {
        // XXX need to look at source to find this function...
        if ($zapchannel) {
            return $this->send_request('ZapDNDoff', array('ZapChannel'=>$zapchannel));
        } else {
            return $this->send_request('ZapDNDoff');
        }
    }

    /**
    * Toggle Zap channel Do Not Disturb status ON
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDNDon
    * @param string $zapchannel
    */
    function ZapDNDon($zapchannel = '') {
        // XXX need to look at source to find this function...
        if ($zapchannel) {
            return $this->send_request('ZapDNDon', array('ZapChannel'=>$zapchannel));
        } else {
            return $this->send_request('ZapDNDon');
        }
    }

    /**
    * Hangup Zap Channel
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapHangup
    * @param string $zapchannel
    */
    function ZapHangup($zapchannel = '') {
        // XXX need to look at source to find this function...
        if ($zapchannel) {
            return $this->send_request('ZapHangup', array('ZapChannel'=>$zapchannel));
        } else {
            return $this->send_request('ZapHangup');
        }
    }

    /**
    * Transfer Zap Channel
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapTransfer
    * @param string $zapchannel
    */
    function ZapTransfer($zapchannel = '') {
        // XXX need to look at source to find this function...
        if ($zapchannel) {
            return $this->send_request('ZapTransfer', array('ZapChannel'=>$zapchannel));
        } else {
            return $this->send_request('ZapTransfer');
        }
    }
    /**
    * Zap Show Channels
    *
    * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapShowChannels
    * @param string $actionid message matching variable
    */
    function ZapShowChannels($actionid=NULL) {
        if($actionid) {
            return $this->send_request('ZapShowChannels', array('ActionID'=>$actionid));
        } else {
            return $this->send_request('ZapShowChannels');
        }
    }

    /*
    * Log a message
    *
    * @param string $message
    * @param integer $level from 1 to 4
    */
    function log($message, $level = 1) {
        if($this->pagi != false) {
            $this->pagi->conlog($message, $level);
        } elseif ($this->log_level === false && $level <= $this->log_level) {
            if (function_exists('dbug')) {
                dbug('php-asmanager log', $message);
            } else {
                error_log(date('r') . ' - ' . $message);
            }
        }
    }

    /**
    * Add event handler
    *
    * Known Events include ( http://www.voip-info.org/wiki-asterisk+manager+events )
    *   Link - Fired when two voice channels are linked together and voice data exchange commences.
    *   Unlink - Fired when a link between two voice channels is discontinued, for example, just before call completion.
    *   Newexten -
    *   Hangup -
    *   Newchannel -
    *   Newstate -
    *   Reload - Fired when the "RELOAD" console command is executed.
    *   Shutdown -
    *   ExtensionStatus -
    *   Rename -
    *   Newcallerid -
    *   Alarm -
    *   AlarmClear -
    *   Agentcallbacklogoff -
    *   Agentcallbacklogin -
    *   Agentlogoff -
    *   MeetmeJoin -
    *   MessageWaiting -
    *   join -
    *   leave -
    *   AgentCalled -
    *   ParkedCall - Fired after ParkedCalls
    *   Cdr -
    *   ParkedCallsComplete -
    *   QueueParams -
    *   QueueMember -
    *   QueueStatusEnd -
    *   Status -
    *   StatusComplete -
    *   ZapShowChannels -  Fired after ZapShowChannels
    *   ZapShowChannelsComplete -
    *
    * @param string $event type or * for default handler
    * @param string $callback function
    * @return boolean sucess
    */
    function add_event_handler($event, $callback) {
        $event = strtolower($event);
        $this->event_handlers[$event][] = $callback;
        return true;
    }

    /**
    * Process event
    *
    * @access private
    * @param array $parameters
    * @return mixed result of event handler or false if no handler was found
    */
    function process_event($parameters) {
        $ret = false;
        $handlers = array();
        $e = strtolower($parameters['Event']);
        $this->log("Got event... $e");

        if(isset($this->event_handlers[$e])) {
            $handlers = array_merge($handlers, $this->event_handlers[$e]);
        }
        if(isset($this->event_handlers['*'])) {
            $handlers = array_merge($handlers, $this->event_handlers['*']);
        }

        foreach ($handlers as $handler) {
            if(is_callable($handler)){
                if (is_array($handler)) {
                    $this->log('Execute handler ' . get_class($handler[0]) . '::' . $handler[1]);
                    $ret = $handler[0]->$handler[1]($e, $parameters, $this->server, $this->port);
                } else {
                    $this->log("Execute handler $handler");
                    $ret = $handler($e, $parameters, $this->server, $this->port);
                }
            }
        }


        return $ret;
    }

    /** Show all entries in the asterisk database
    * @return Array associative array of key=>value
    */
    function database_show($family='') {
        if ($this->useCaching && $this->memAstDB != null) {
            if ($family == '') {
                return $this->memAstDB;
            } else {
                 $key = '/'.$family;
                if (isset($this->memAstDB[$key])) {
                    return array($key => $this->memAstDB[$key]);
                } else {
                    $key .= '/';
                    $len = strlen($key);
                    $fam_arr = array();
                    foreach ($this->memAstDB as $this_key => $value) {
                        if (substr($this_key,0,$len) ==  $key) {
                            $fam_arr[$this_key] = $value;
                        }
                    }
                    return $fam_arr;
                }
            }
        }
        $r = $this->command("database show $family");

        $data = explode("\n",$r["data"]);
        $db = array();

        // Remove the Privilege => Command initial entry that comes from the heading
        //
        array_shift($data);
        foreach ($data as $line) {
            $temp = explode(":",$line,2);
            if (trim($temp[0]) != '' && count($temp) == 2) {
                $temp[1] = isset($temp[1])?$temp[1]:null;
                $db[ trim($temp[0]) ] = trim($temp[1]);
            }
        }
        return $db;
    }

    /** Add an entry to the asterisk database
     * @param string $family    The family name to use
     * @param string $key        The key name to use
     * @param mixed $value        The value to add
     * @return bool True if successful
     */
    function database_put($family, $key, $value) {
        $value = (trim($value) == '')?'"'.$value.'"':$value;
        $r = $this->command("database put ".str_replace(" ","/",$family)." ".str_replace(" ","/",$key)." ".$value);
        if (!empty($this->memAstDB)){
            $keyUsed="/".str_replace(" ","/",$family)."/".str_replace(" ","/",$key);
            $this->memAstDB[$keyUsed] = $value;
        }
        return (bool)strstr($r["data"], "success");
    }

    /** Get an entry from the asterisk database
     * @param string $family    The family name to use
     * @param string $key        The key name to use
     * @return mixed Value of the key, or false if error
     */
    function database_get($family, $key) {
        if ($this->useCaching) {
            if ($this->memAstDB == null) {
                $this->LoadAstDB();
            }
            $keyUsed="/".str_replace(" ","/",$family)."/".str_replace(" ","/",$key);
            if (array_key_exists($keyUsed,$this->memAstDB)){
                return $this->memAstDB[$keyUsed];
            }
        } else {
            $r = $this->command("database get ".str_replace(" ","/",$family)." ".str_replace(" ","/",$key));
            $data = strpos($r["data"],"Value:");
            if ($data !== false) {
                return trim(substr($r["data"],6+$data));
            }
        }
        return false;
    }


    /** Delete an entry from the asterisk database
     * @param string $family    The family name to use
     * @param string $key        The key name to use
     * @return bool True if successful
     */
    function database_del($family, $key) {
         if (!empty($this->memAstDB)){
            $keyUsed="/".str_replace(" ","/",$family)."/".str_replace(" ","/",$key);
            unset($this->memAstDB[$keyUsed]);
        }
        $r = $this->command("database del ".str_replace(" ","/",$family)." ".str_replace(" ","/",$key));
        return (bool)strstr($r["data"], "removed");
    }

    /** Delete a family from the asterisk database
     * @param string $family    The family name to use
     * @return bool True if successful
     */
    function database_deltree($family) {
        if (!empty($this->memAstDB)){
            $keyUsed="/".str_replace(" ","/",$family);
            unset($this->memAstDB[$keyUsed]);
        }
        $r = $this->command("database deltree ".str_replace(" ","/",$family));
        return (bool)strstr($r["data"], "removed");
    }

    /** Returns whether a give function exists in this Asterisk install
     * @param string $func    The case sensitve name of the function
     * @return bool True if if it exists
     */
    function func_exists($func) {
        $r = $this->command("core show function $func");
        return (strpos($r['data'],"No function by that name registered") === false);
    }

    /** Returns whether a give application exists in this Asterisk install
     * @param string $app    The case in-sensitve name of the application
     * @return bool True if if it exists
     */
    function app_exists($app) {
        $r = $this->command("core show application $app");
        return (strpos($r['data'],"Your application(s) is (are) not registered") === false);
    }

    /** Returns whether a give channeltype exists in this Asterisk install
     * @param string $channel    The case in-sensitve name of the channel
     * @return bool True if if it exists
     */
    function chan_exists($channel) {
        $r = $this->command("core show channeltype $channel");
        return (strpos($r['data'],"is not a registered channel driver") === false);
    }

    /** Returns whether a give asterisk module is loaded in this Asterisk install
     * @param string $app    The case in-sensitve name of the application
     * @return bool True if if it exists
     */
    function mod_loaded($mod) {
        $r = $this->command("module show like $mod");
        return (preg_match('/1 modules loaded/', $r['data']) > 0);
    }

    /** Sets a global var or function to the provided value
     * @param string $var    The variable or function to set
     * @param string $val    the value to set it to
     * @return array returns the array value from the send_request
     */
    function set_global($var, $val) {
        global $amp_conf;
        static $pre = '';

        if (! $pre) {
            $pre = version_compare($amp_conf['ASTVERSION'], "1.6.1", "ge") ? 'dialplan' : 'core';
        }
        return $this->command($pre . ' set global ' . $var . ' ' . $val);
    }

    /**
    * Reload module(s)
    *
    * @link http://www.voip-info.org/wiki/view/Asterisk+Manager+API+Action+Reload
    * @param string $module
    * @param string $actionid
    */
    function Reload($module=NULL, $actionid=NULL) {
        $parameters = array();

        if ($actionid) {
            $parameters['ActionID'] = $actionid;
        }
        if ($module) {
            $parameters['Module'] = $module;
        }
        return $this->send_request('Reload', $parameters);
    }

    /** Starts mixmonitor
     * @param string $channel    The channel to start recording
     * @param string $file The file to record to
     * @param string $options Options to pass to mixmonitor
     * @param string $postcommand Command to execute after recording
     * @param string $actionid message matching variable
     *
     * @return array returns the array value from the send_request
     */
    function mixmonitor($channel, $file, $options='', $postcommand='', $actionid=NULL) {
        if (!$channel || !$file) {
            return false;
        }
        $args = 'mixmonitor start ' . trim($channel) . ' ' . trim($file);
        if ($options || $postcommand) {
            $args .= ',' . trim($options);
        }
        if ($postcommand) {
            $args .= ',' . trim($postcommand);
        }
        return $this->command($args, $actionid);
    }

    /** Stops mixmonitor
     * @param string $channel    The channel to stop recording
     * @param string $actionid message matching variable
     *
     * @return array returns the array value from the send_request
     */
    function stopmixmonitor($channel, $actionid=NULL) {
        if (!$channel) {
            return false;
        }
        $args = 'mixmonitor stop ' . trim($channel);
        return $this->command($args, $actionid);
    }
}
?>
